package validate

import (
	"errors"
	"fmt"
	"github.com/jmespath/go-jmespath"
	"gitverse.ru/synapse/kubelatte/pkg/api/common"
	"gitverse.ru/synapse/kubelatte/pkg/api/v1alpha1"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/match"
	"gitverse.ru/synapse/kubelatte/pkg/opa"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"reflect"
	"regexp"
)

type Namespace struct {
	Name        string
	Labels      map[string]string
	Annotations map[string]string
}

func Iterator(elem interface{}, rulePath string, ruleValue string, isReverse bool) bool {
	val := reflect.ValueOf(elem)

	tp := reflect.TypeOf(elem)
	if !val.IsValid() || val.IsZero() {
		return false
	}
	switch tp.Kind() {
	case reflect.Slice:
		{
			length := val.Len()
			for i := 0; i < length; i++ {
				if isReverse {
					if Iterator(val.Index(i).Interface(), rulePath, ruleValue, isReverse) {
						return true
					}
				} else {
					if !Iterator(val.Index(i).Interface(), rulePath, ruleValue, isReverse) {
						return false
					}
				}
			}
		}

	case reflect.Map:
		{
			keys := val.MapKeys()
			for _, v := range keys {
				value := val.MapIndex(v)
				if isReverse {
					if Iterator(value.Interface(), rulePath, ruleValue, isReverse) {
						return true
					}
				} else {
					if !Iterator(value.Interface(), rulePath, ruleValue, isReverse) {
						return false
					}
				}
			}

		}
	case reflect.Struct:
		{
			for i := 0; i < val.NumField(); i++ {
				f := val.Field(i)
				logs.Error(f.Interface())
			}

		}

	case reflect.String:
		{
			scope := ruleValue

			str, ok := elem.(string)

			if !ok {
				return false
			}
			o, err := regexp.Match(ruleValue, []byte(str))
			if err != nil {
				logs.Errorf("Scope validation error %v", err)
			}
			if isReverse {
				if o {
					logs.Debugf("value: %s match scope: %s, ok\n", str, scope)
					return true
				}
			} else {
				if !o {
					logs.Debugf("value: %s not match with scope: %s, path: %s failed\n", str, scope, rulePath)
					return false
				}
				logs.Debugf("value: %s match scope: %s, ok\n", str, scope)
			}
		}
	default:
		return isReverse
	}

	return !isReverse
}

func CheckScope(data map[string]interface{}, items []v1alpha1.Item) error {
	var allowed bool

	for _, item := range items {

		p := item.Path

		result, err := jmespath.Search(p, data)
		if err != nil {
			continue
		}

		if !reflect.ValueOf(result).IsValid() || reflect.ValueOf(result).Len() == 0 {
			continue
		}
		if result != nil && item.Value == ".*" {
			allowed = true
			continue
		}
		if !Iterator(result, item.Path, item.Value, false) {
			allowed = false
			return fmt.Errorf("failed path:%s", p)
		}
		allowed = true
	}
	if !allowed {
		return fmt.Errorf("no valid scope")
	}

	return nil
}

func ValidateResource(original map[string]interface{}, requestData common.ARFields, items []v1alpha1.Item) error {
	for _, item := range items {

		if len(item.Match.Kinds) == 0 || len(item.Match.Kinds[0].Kind) == 0 || len(item.Match.Kinds[0].ApiGroups) == 0 {
			matches := CompareApiVersionAndKind(item, requestData.Kind)
			if !matches {
				continue
			}
		}

		if item.Rule.Simples != nil || !reflect.DeepEqual(v1alpha1.Match{}, item.Match) {

			allowed := match.MatchCompatibility(requestData, item.Match)
			if !allowed {
				continue
			}

			regoCheck := CheckRegoRule(original, item)
			if regoCheck != nil {
				return regoCheck
			}

			if item.Rule.Simples == nil {
				continue
			}

			simplesCheck := CheckSimplesRule(item, requestData)
			if simplesCheck != nil {
				return simplesCheck
			}
		} else {
			regularCheck := CheckRegularRule(requestData, item)
			if regularCheck != nil {
				return regularCheck
			}

		}
	}
	return nil
}

func CheckRegularRule(requestData common.ARFields, item v1alpha1.Item) error {
	p := item.Path
	result, err := jmespath.Search(p, requestData.Object)

	if err != nil {
		return nil
	}

	if !reflect.ValueOf(result).IsValid() || reflect.ValueOf(result).Len() == 0 {
		return nil
	}

	validationSuccess := validateValue(result, item.Path, item.Value, Deny)
	if !validationSuccess {
		return fmt.Errorf("validation failed: rule %s path %s", item.Value, item.Path)
	}
	return nil
}

func CheckSimplesRule(item v1alpha1.Item, requestData common.ARFields) error {
	var simplesErr error

	for _, simple := range item.Rule.Simples {

		var object map[string]interface{}
		if requestData.Operation != "DELETE" {
			object = requestData.Object
		} else {
			object = requestData.OldObject
		}

		result, err := jmespath.Search(simple.Path, object)
		if err != nil {
			continue
		}

		if !reflect.ValueOf(result).IsValid() || reflect.ValueOf(result).Len() == 0 {
			continue
		}

		validationSuccess := validateValue(result, simple.Path, simple.Value, simple.Action)
		if !validationSuccess {

			message := simple.Message
			if simple.Message == "" {
				message = fmt.Sprintf("validation failed: rule %s path %s", simple.Value, simple.Path)
			}

			if simplesErr == nil {
				simplesErr = errors.New(message)
				continue
			}

			simplesErr = fmt.Errorf("%s; %s", simplesErr.Error(), message)
		}
	}
	if simplesErr != nil {
		return simplesErr
	}
	return nil
}

func CheckRegoRule(original map[string]interface{}, item v1alpha1.Item) error {
	if item.Rule.Rego.Template != "" {

		params, err := fromYAML([]byte(item.Rule.Rego.Parameters))
		if err != nil {
			return err
		}

		result, details := opa.OpaController.CheckRule(item.Rule.Rego.Template, params, original)
		if !result {
			return fmt.Errorf("validation failed: rule %s details %v", item.Name, details)
		}
	}
	return nil
}

func validateValue(result interface{}, path, value, action string) bool {
	if result != nil && value == ".*" {
		if action == Deny {
			return false
		}
		if action == Allow {
			return true
		}
	}

	if action == Deny {
		if Iterator(result, path, value, true) {
			logs.Debugf("validation failed: rule %s path %s", value, path)
			return false
		}
	}
	if action == Allow {
		if !Iterator(result, path, value, false) {
			logs.Debugf("validation failed: rule %s path %s", value, path)
			return false
		}
	}

	return true
}

func CompareApiVersionAndKind(rule v1alpha1.Item, kind v1.GroupVersionKind) bool {
	if rule.Kind != kind.Kind || rule.ApiVersion != getVersion(kind.Group, kind.Version) {
		return false
	} else {
		return true
	}
}
